/*
 *  Copyright 2012 Dirk Vranckaert
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package eu.vranckaert.vvl.utils.context;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import eu.vranckaert.vvl.model.TrafficInfoAlarm;
import eu.vranckaert.vvl.model.TrafficInfoGraphicalEnum;
import eu.vranckaert.vvl.model.TrafficInfoTextEnum;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;

/**
 * Access all the preferences. This class is mainly used to read the preferences but can be used in some rare cases
 * to also update or insert preferences.
 *
 * User: DIRK VRANCKAERT
 * Date: 17/02/12
 * Time: 13:58
 */
public class Preference {
    private static final String LOG_TAG = Preference.class.getSimpleName();
    
    /**
     * Get an instance of {@link android.content.SharedPreferences} to access the preferences.
     * @param ctx The context when accessing the preferences.
     * @return The instance based on the context.
     */
    private static final SharedPreferences getSharedPreferences(Context ctx) {
        return ctx.getSharedPreferences(Constants.Preferences.PREFERENCES_NAME, Activity.MODE_PRIVATE);
    }

    /**
     * Remove a certain preference from the system.
     * @param ctx The context.
     * @param key The key of the preference to remove.
     */
    public static final void removePreference(Context ctx, String key) {
        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.remove(key);
        editor.commit();
    }

    /**
     * Get the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_INFORMATION_TYPES}. If no information types
     * are selected return an empty list.
     * @param ctx The context when getting the selected information types.
     * @return A list of {@link eu.vranckaert.vvl.model.TrafficInfoTextEnum.InformationType} objects.
     */
    public static List<TrafficInfoTextEnum.InformationType> getSelectedTextInformationTypes(Context ctx) {
        StringBuilder allInformationTypes = new StringBuilder();
        for (TrafficInfoTextEnum.InformationType infoType : TrafficInfoTextEnum.InformationType.values()) {
            allInformationTypes.append(infoType.getValue() + Constants.LIST_SEPERATOR);
        }
        allInformationTypes.replace(allInformationTypes.length()-Constants.LIST_SEPERATOR.length(), allInformationTypes.length(), "");
        
        String selectedInformationTypes = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SELECTED_TEXT_INFORMATION_TYPES, allInformationTypes.toString());
        String[] selectedInformationTypesArray = selectedInformationTypes.split(Constants.LIST_SEPERATOR);

        List<TrafficInfoTextEnum.InformationType> informationTypes = new ArrayList<TrafficInfoTextEnum.InformationType>();
        for (String type : selectedInformationTypesArray) {
            TrafficInfoTextEnum.InformationType informationType = TrafficInfoTextEnum.InformationType.getByValue(type);
            informationTypes.add(informationType);
        }
        return informationTypes;
    }

    /**
     * set the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_INFORMATION_TYPES}. If no information types
     * are set the list will be emptied.
     * @param ctx The context when setting the selected information types.
     * @param informationTypes The list of types to set.
     */
    public static void setSelectedTextInformationTypes(Context ctx, List<TrafficInfoTextEnum.InformationType> informationTypes) {
        String selectedInformationTypes = "";
        for (TrafficInfoTextEnum.InformationType informationType : informationTypes) {
            selectedInformationTypes += informationType.getValue() + Constants.LIST_SEPERATOR;
        }
        if (selectedInformationTypes.length() > 0) {
            selectedInformationTypes = selectedInformationTypes.substring(0, selectedInformationTypes.length()-1);
        }

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.SELECTED_TEXT_INFORMATION_TYPES, selectedInformationTypes);
        editor.commit();
    }

    /**
     * Get the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_REGIONS}. If no text regions are
     * selected return an empty list.
     * @param ctx The context when getting the selected regions.
     * @return A list of {@link eu.vranckaert.vvl.model.TrafficInfoTextEnum.Region} objects.
     */
    public static List<TrafficInfoTextEnum.Region> getSelectedTextRegions(Context ctx) {
        StringBuilder allRegions = new StringBuilder();
        for (TrafficInfoTextEnum.Region region : TrafficInfoTextEnum.Region.values()) {
            allRegions.append(region.getValue() + Constants.LIST_SEPERATOR);
        }
        allRegions.replace(allRegions.length()-Constants.LIST_SEPERATOR.length(), allRegions.length(), "");

        String selectedRegions = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SELECTED_TEXT_REGIONS, allRegions.toString());
        String[] selectedRegionsArray = selectedRegions.split(Constants.LIST_SEPERATOR);

        List<TrafficInfoTextEnum.Region> regions = new ArrayList<TrafficInfoTextEnum.Region>();
        for (String type : selectedRegionsArray) {
            if (type != null && type.length() > 0) {
                TrafficInfoTextEnum.Region region = null;
                try {
                    region = TrafficInfoTextEnum.Region.getByValue(Integer.parseInt(type));
                    regions.add(region);
                } catch (NumberFormatException e) {
                    Log.w(LOG_TAG, "Could not parse the type" + type + " to a number!", e);
                }
            }
        }
        return regions;
    }

    /**
     * set the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_REGIONS}. If no text regions are set
     * the list will be emptied.
     * @param ctx The context when setting the selected regions.
     * @param regions The list of regions to set.
     */
    public static void setSelectedTextRegions(Context ctx, List<TrafficInfoTextEnum.Region> regions) {
        String selectedRegions = "";
        for (TrafficInfoTextEnum.Region region : regions) {
            selectedRegions += region.getValue() + Constants.LIST_SEPERATOR;
        }
        if (selectedRegions.length() > 0) {
            selectedRegions = selectedRegions.substring(0, selectedRegions.length()-1);
        }

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.SELECTED_TEXT_REGIONS, selectedRegions);
        editor.commit();
    }

    /**
     * Get the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_HIGHROADS}. If no text highroads are
     * selected return an empty list.
     * @param ctx The context when getting the selected highroads.
     * @return A list of {@link eu.vranckaert.vvl.model.TrafficInfoTextEnum.Highroad} objects.
     */
    public static List<TrafficInfoTextEnum.Highroad> getSelectedTextHighroads(Context ctx) {
        String selectedHighroads = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SELECTED_TEXT_HIGHROADS, Constants.Preferences.DefaultValues.SELECTED_TEXT_HIGHROADS);
        String[] selectedHighroadsArray = selectedHighroads.split(Constants.LIST_SEPERATOR);

        List<TrafficInfoTextEnum.Highroad> highroads = new ArrayList<TrafficInfoTextEnum.Highroad>();
        for (String hr : selectedHighroadsArray) {
            if (hr != null && hr.length() > 0) {
                TrafficInfoTextEnum.Highroad highroad = null;
                try {
                    highroad = TrafficInfoTextEnum.Highroad.getByValue(Integer.parseInt(hr));
                    highroads.add(highroad);
                } catch (NumberFormatException e) {
                    Log.w(LOG_TAG, "Could not parse the highroad" + hr + " to a number!", e);
                }
            }
        }
        return highroads;
    }

    /**
     * set the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_HIGHROADS}. If no text highroads are
     * set the list will be emptied.
     * @param ctx The context when setting the selected highroads.
     * @param highroads The list of highroads to set.
     */
    public static void setSelectedTextHighroads(Context ctx, List<TrafficInfoTextEnum.Highroad> highroads) {
        String selectedHighroads = "";
        for (TrafficInfoTextEnum.Highroad highroad : highroads) {
            selectedHighroads += highroad.getValue() + Constants.LIST_SEPERATOR;
        }
        if (selectedHighroads.length() > 0) {
            selectedHighroads = selectedHighroads.substring(0, selectedHighroads.length()-1);
        }

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.SELECTED_TEXT_HIGHROADS, selectedHighroads);
        editor.commit();
    }

    /**
     * Get the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_SECONDARY_ROADS}. If no text secondary
     * roads are selected return an empty list.
     * @param ctx The context when getting the selected secondary roads.
     * @return A list of {@link eu.vranckaert.vvl.model.TrafficInfoTextEnum.SecondaryRoads} objects.
     */
    public static List<TrafficInfoTextEnum.SecondaryRoads> getSelectedTextSecondaryRoads(Context ctx) {
        String selectedSecondaryRoads = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SELECTED_TEXT_SECONDARY_ROADS,
                Constants.Preferences.DefaultValues.SELECTED_TEXT_SECONDARY_ROADS);
        String[] selectedSecondaryRoadsArray = selectedSecondaryRoads.split(Constants.LIST_SEPERATOR);

        List<TrafficInfoTextEnum.SecondaryRoads> secondaryRoads = new ArrayList<TrafficInfoTextEnum.SecondaryRoads>();
        for (String sr : selectedSecondaryRoadsArray) {
            if (sr != null && sr.length() > 0) {
                TrafficInfoTextEnum.SecondaryRoads secondaryRoad = null;
                try {
                    secondaryRoad = TrafficInfoTextEnum.SecondaryRoads.getByValue(Integer.parseInt(sr));
                    secondaryRoads.add(secondaryRoad);
                } catch (NumberFormatException e) {
                    Log.w(LOG_TAG, "Could not parse the secondary road" + sr + " to a number!", e);
                }
            }
        }
        return secondaryRoads;
    }

    /**
     * set the preference for key {@link Constants.Preferences.Keys#SELECTED_TEXT_SECONDARY_ROADS}. If no text
     * secondaryRoads are set the list will be emptied.
     * @param ctx The context when setting the selected secondaryRoads.
     * @param secondaryRoads The list of secondaryRoads to set.
     */
    public static void setSelectedTextSecondaryRoads(Context ctx, List<TrafficInfoTextEnum.SecondaryRoads> secondaryRoads) {
        String selectedSecondaryRoads = "";
        for (TrafficInfoTextEnum.SecondaryRoads secondaryRoad : secondaryRoads) {
            selectedSecondaryRoads += secondaryRoad.getValue() + Constants.LIST_SEPERATOR;
        }
        if (selectedSecondaryRoads.length() > 0) {
            selectedSecondaryRoads = selectedSecondaryRoads.substring(0, selectedSecondaryRoads.length()-1);
        }

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.SELECTED_TEXT_SECONDARY_ROADS, selectedSecondaryRoads);
        editor.commit();
    }

    /**
     * Get the preference for key {@link Constants.Preferences.Keys#SELECTED_GRAPHICAL_REGION}. If no graphical region
     * is selected return null!
     * @param ctx The context when getting the selected regions.
     * @return The selected {@link eu.vranckaert.vvl.model.TrafficInfoGraphicalEnum.Region}.
     */
    public static TrafficInfoGraphicalEnum.Region getSelectedGraphicalRegion(Context ctx) {
        String selectedRegion = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SELECTED_GRAPHICAL_REGION, Constants.Preferences.DefaultValues.SELECTED_GRAPHICAL_REGION);
        TrafficInfoGraphicalEnum.Region region = TrafficInfoGraphicalEnum.Region.getByValue(selectedRegion);
        return region;
    }

    /**
     * set the preference for key {@link Constants.Preferences.Keys#SELECTED_GRAPHICAL_REGION}. If no graphical region
     * is set the preference will be removed!
     * @param ctx The context when setting the selected region.
     * @param region The region to set (or null to remove the preference).
     */
    public static void setSelectedGraphicalRegion(Context ctx, TrafficInfoGraphicalEnum.Region region) {
        if (region == null) {
            removePreference(ctx, Constants.Preferences.Keys.SELECTED_GRAPHICAL_REGION);
            return;
        }

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.SELECTED_GRAPHICAL_REGION, region.getValue());
        editor.commit();
    }

    /**
     * Get the next alarm request code to be used.
     * @param ctx The context when getting (and updating) the alarm request code.
     * @return The incremented (so a new and unique) alarm request code.
     */
    public static int getNextAlarmRequestCode(Context ctx) {
        int requestCode = getSharedPreferences(ctx).getInt(Constants.Preferences.Keys.ALARM_REQUEST_CODE, Constants.Preferences.DefaultValues.ALARM_REQUEST_CODE);
        requestCode++;

        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putInt(Constants.Preferences.Keys.ALARM_REQUEST_CODE, requestCode);
        editor.commit();

        return requestCode;
    }

    /**
     * Add a new alarm to the preferences.
     * @param ctx The context when adding an alarm.
     * @param alarm The alarm to add.
     */
    public static void addAlarm(Context ctx, TrafficInfoAlarm alarm) {
        List<TrafficInfoAlarm> trafficInfoAlarms = getAlarms(ctx);
        trafficInfoAlarms.add(alarm);
        setAlarms(ctx, trafficInfoAlarms);
    }

    /**
     * Deletes an alarm from the preferences.
     * @param ctx ctx The context when deleting an alarm.
     * @param alarm The alarm to be deleted.
     */
    public static void deleteAlarm(Context ctx, TrafficInfoAlarm alarm) {
        if (alarm != null) {
            List<TrafficInfoAlarm> alarms = new ArrayList<TrafficInfoAlarm>();
            alarms.add(alarm);
            deleteAlarms(ctx, alarms);
        }
    }

    /**
     * Deletes a list of alarms from the preferences.
     * @param ctx ctx The context when deleting alarms.
     * @param alarms The alarms to be deleted.
     */
    public static void deleteAlarms(Context ctx, List<TrafficInfoAlarm> alarms) {
        List<Integer> removedIndexes = new ArrayList<Integer>();
        
        String availableAlarms = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SAVED_ALARMS, Constants.Preferences.DefaultValues.SAVED_ALARMS);
        List<String> alarmStrings = Arrays.asList(availableAlarms.split(Constants.LIST_SEPERATOR));
        
        for (TrafficInfoAlarm alarm : alarms) {
            if (alarm != null && alarmStrings.contains(alarm.toString())) {
                removedIndexes.add(alarmStrings.indexOf(alarm.toString()));
            }
        }

        StringBuilder sb = new StringBuilder();
        for (String alarmString : alarmStrings) {
            if (!removedIndexes.contains(alarmStrings.indexOf(alarmString))) {
                sb.append(alarmString);
                sb.append(Constants.LIST_SEPERATOR);
            }
        }

        if (sb.length() > 0 && sb.charAt(sb.length()-1) == Constants.LIST_SEPERATOR.charAt(0)) {
            sb.substring(0, sb.length()-2);
        }

        if (sb.length() > 0) {
            SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
            editor.putString(Constants.Preferences.Keys.SAVED_ALARMS, sb.toString());
            editor.commit();
        }
    }

    /**
     * Get all the alarms for the application from the preferences.
     * @param ctx The context for getting the alarms.
     * @return A list of {@link TrafficInfoAlarm} instance.
     */
    public static List<TrafficInfoAlarm> getAlarms(Context ctx) {
        String savedAlarms = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.SAVED_ALARMS, Constants.Preferences.DefaultValues.SAVED_ALARMS);
        String[] alarms = savedAlarms.split(Constants.LIST_SEPERATOR);
        
        List<TrafficInfoAlarm> alarmsToBeDeleted = new ArrayList<TrafficInfoAlarm>();
        
        List<TrafficInfoAlarm> trafficInfoAlarms = new ArrayList<TrafficInfoAlarm>();
        for (String alarm : alarms) {
            TrafficInfoAlarm trafficInfoAlarm = TrafficInfoAlarm.fromString(alarm);
            if (trafficInfoAlarm != null) {
                boolean addAlarm = true;
                if (!trafficInfoAlarm.isRepeatingAlarm() && trafficInfoAlarm.getAlarmTime().before(Calendar.getInstance())) {
                    addAlarm = false;
                }
                
                if (addAlarm) {
                    trafficInfoAlarms.add(trafficInfoAlarm);
                }
            }
        }

        if (alarmsToBeDeleted != null && !alarmsToBeDeleted.isEmpty()) {
            deleteAlarms(ctx, alarmsToBeDeleted);
        }

        return trafficInfoAlarms;
    }

    /**
     * Set all the alarms for the application from the preferences.
     * @param ctx The context when setting the alarms.
     * @param trafficInfoAlarms The list of {@link TrafficInfoAlarm} to set.
     */
    public static void setAlarms(Context ctx, List<TrafficInfoAlarm> trafficInfoAlarms) {
        StringBuilder sb = new StringBuilder();
        
        for (TrafficInfoAlarm trafficInfoAlarm : trafficInfoAlarms) {
            sb.append(trafficInfoAlarm.toString());
            sb.append(Constants.LIST_SEPERATOR);
        }

        if (sb.length() > 0 && sb.charAt(sb.length()-1) == Constants.LIST_SEPERATOR.charAt(0)) {
            sb.substring(0, sb.length()-2);
        }
        
        if (sb.length() > 0) {
            SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
            editor.putString(Constants.Preferences.Keys.SAVED_ALARMS, sb.toString());
            editor.commit();
        }
    }

    /**
     * Get the image size for graphical traffic information.
     * @param ctx The context when getting the image size.
     * @return The {@link TrafficInfoGraphicalEnum.ImageSize}.
     */
    public static TrafficInfoGraphicalEnum.ImageSize getTrafficInfoGraphicalImageSize(Context ctx) {
        String imageSize = getSharedPreferences(ctx).getString(Constants.Preferences.Keys.TRAFFIC_INFO_GRAPHICAL_IMAGE_SIZE, Constants.Preferences.DefaultValues.TRAFFIC_INFO_GRAPHICAL_IMAGE_SIZE);
        return TrafficInfoGraphicalEnum.ImageSize.valueOf(imageSize);
    }

    /**
     * Set the preference for key {@link Constants.Preferences.Keys#TRAFFIC_INFO_GRAPHICAL_IMAGE_SIZE}. If no graphical region
     * is set the preference will be removed!
     * @param ctx The context when setting the selected region.
     * @param imageSize The region to set (or null to remove the preference).
     */
    public static void setTrafficInfoGraphicalImageSize(Context ctx, TrafficInfoGraphicalEnum.ImageSize imageSize) {
        SharedPreferences.Editor editor = getSharedPreferences(ctx).edit();
        editor.putString(Constants.Preferences.Keys.TRAFFIC_INFO_GRAPHICAL_IMAGE_SIZE, imageSize.toString());
        editor.commit();
    }
}